// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdint.h>
#include <string.h>

#include <stack>
#include <string>
#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/pickle.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/appcache/appcache_response.h"
#include "content/browser/appcache/mock_appcache_service.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "testing/gtest/include/gtest/gtest.h"

using net::IOBuffer;
using net::WrappedIOBuffer;

namespace content {

static const int kNumBlocks = 4;
static const int kBlockSize = 1024;
static const int kNoSuchResponseId = 123;

class AppCacheResponseTest : public testing::Test {
public:
    // Test Harness -------------------------------------------------------------

    // Helper class used to verify test results
    class MockStorageDelegate : public AppCacheStorage::Delegate {
    public:
        explicit MockStorageDelegate(AppCacheResponseTest* test)
            : loaded_info_id_(0)
            , test_(test)
        {
        }

        void OnResponseInfoLoaded(AppCacheResponseInfo* info,
            int64_t response_id) override
        {
            loaded_info_ = info;
            loaded_info_id_ = response_id;
            test_->ScheduleNextTask();
        }

        scoped_refptr<AppCacheResponseInfo> loaded_info_;
        int64_t loaded_info_id_;
        AppCacheResponseTest* test_;
    };

    // Helper callback to run a test on our io_thread. The io_thread is spun up
    // once and reused for all tests.
    template <class Method>
    void MethodWrapper(Method method)
    {
        SetUpTest();
        (this->*method)();
    }

    static void SetUpTestCase()
    {
        io_thread_.reset(new base::Thread("AppCacheResponseTest Thread"));
        base::Thread::Options options(base::MessageLoop::TYPE_IO, 0);
        io_thread_->StartWithOptions(options);
    }

    static void TearDownTestCase()
    {
        io_thread_.reset(NULL);
    }

    AppCacheResponseTest() { }

    template <class Method>
    void RunTestOnIOThread(Method method)
    {
        test_finished_event_.reset(new base::WaitableEvent(
            base::WaitableEvent::ResetPolicy::AUTOMATIC,
            base::WaitableEvent::InitialState::NOT_SIGNALED));
        io_thread_->task_runner()->PostTask(
            FROM_HERE, base::Bind(&AppCacheResponseTest::MethodWrapper<Method>, base::Unretained(this), method));
        test_finished_event_->Wait();
    }

    void SetUpTest()
    {
        DCHECK(io_thread_->task_runner()->BelongsToCurrentThread());
        DCHECK(task_stack_.empty());
        storage_delegate_.reset(new MockStorageDelegate(this));
        service_.reset(new MockAppCacheService());
        expected_read_result_ = 0;
        expected_write_result_ = 0;
        written_response_id_ = 0;
        should_delete_reader_in_completion_callback_ = false;
        should_delete_writer_in_completion_callback_ = false;
        reader_deletion_count_down_ = 0;
        writer_deletion_count_down_ = 0;
        read_callback_was_called_ = false;
        write_callback_was_called_ = false;
    }

    void TearDownTest()
    {
        DCHECK(io_thread_->task_runner()->BelongsToCurrentThread());
        while (!task_stack_.empty())
            task_stack_.pop();

        reader_.reset();
        read_buffer_ = NULL;
        read_info_buffer_ = NULL;
        writer_.reset();
        write_buffer_ = NULL;
        write_info_buffer_ = NULL;
        storage_delegate_.reset();
        service_.reset();
    }

    void TestFinished()
    {
        // We unwind the stack prior to finishing up to let stack
        // based objects get deleted.
        DCHECK(io_thread_->task_runner()->BelongsToCurrentThread());
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE, base::Bind(&AppCacheResponseTest::TestFinishedUnwound, base::Unretained(this)));
    }

    void TestFinishedUnwound()
    {
        TearDownTest();
        test_finished_event_->Signal();
    }

    void PushNextTask(const base::Closure& task)
    {
        task_stack_.push(std::pair<base::Closure, bool>(task, false));
    }

    void PushNextTaskAsImmediate(const base::Closure& task)
    {
        task_stack_.push(std::pair<base::Closure, bool>(task, true));
    }

    void ScheduleNextTask()
    {
        DCHECK(io_thread_->task_runner()->BelongsToCurrentThread());
        if (task_stack_.empty()) {
            TestFinished();
            return;
        }
        base::Closure task = task_stack_.top().first;
        bool immediate = task_stack_.top().second;
        task_stack_.pop();
        if (immediate)
            task.Run();
        else
            base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, task);
    }

    // Wrappers to call AppCacheResponseReader/Writer Read and Write methods

    void WriteBasicResponse()
    {
        static const char kHttpHeaders[] = "HTTP/1.0 200 OK\0Content-Length: 5\0\0";
        static const char kHttpBody[] = "Hello";
        scoped_refptr<IOBuffer> body(new WrappedIOBuffer(kHttpBody));
        std::string raw_headers(kHttpHeaders, arraysize(kHttpHeaders));
        WriteResponse(
            MakeHttpResponseInfo(raw_headers), body.get(), strlen(kHttpBody));
    }

    int basic_response_size() { return 5; } // should match kHttpBody above

    void WriteResponse(net::HttpResponseInfo* head,
        IOBuffer* body, int body_len)
    {
        DCHECK(body);
        scoped_refptr<IOBuffer> body_ref(body);
        PushNextTask(base::Bind(&AppCacheResponseTest::WriteResponseBody,
            base::Unretained(this), body_ref, body_len));
        WriteResponseHead(head);
    }

    void WriteResponseHead(net::HttpResponseInfo* head)
    {
        EXPECT_FALSE(writer_->IsWritePending());
        expected_write_result_ = GetHttpResponseInfoSize(head);
        write_info_buffer_ = new HttpResponseInfoIOBuffer(head);
        writer_->WriteInfo(write_info_buffer_.get(),
            base::Bind(&AppCacheResponseTest::OnWriteInfoComplete,
                base::Unretained(this)));
    }

    void WriteResponseBody(scoped_refptr<IOBuffer> io_buffer, int buf_len)
    {
        EXPECT_FALSE(writer_->IsWritePending());
        write_buffer_ = io_buffer;
        expected_write_result_ = buf_len;
        writer_->WriteData(write_buffer_.get(),
            buf_len,
            base::Bind(&AppCacheResponseTest::OnWriteComplete,
                base::Unretained(this)));
    }

    void WriteResponseMetadata(scoped_refptr<IOBuffer> io_buffer, int buf_len)
    {
        EXPECT_FALSE(metadata_writer_->IsWritePending());
        write_buffer_ = io_buffer;
        expected_write_result_ = buf_len;
        metadata_writer_->WriteMetadata(
            write_buffer_.get(), buf_len,
            base::Bind(&AppCacheResponseTest::OnMetadataWriteComplete,
                base::Unretained(this)));
    }

    void ReadResponseBody(scoped_refptr<IOBuffer> io_buffer, int buf_len)
    {
        EXPECT_FALSE(reader_->IsReadPending());
        read_buffer_ = io_buffer;
        expected_read_result_ = buf_len;
        reader_->ReadData(read_buffer_.get(),
            buf_len,
            base::Bind(&AppCacheResponseTest::OnReadComplete,
                base::Unretained(this)));
    }

    // AppCacheResponseReader / Writer completion callbacks

    void OnWriteInfoComplete(int result)
    {
        EXPECT_FALSE(writer_->IsWritePending());
        EXPECT_EQ(expected_write_result_, result);
        ScheduleNextTask();
    }

    void OnWriteComplete(int result)
    {
        EXPECT_FALSE(writer_->IsWritePending());
        write_callback_was_called_ = true;
        EXPECT_EQ(expected_write_result_, result);
        if (should_delete_writer_in_completion_callback_ && --writer_deletion_count_down_ == 0) {
            writer_.reset();
        }
        ScheduleNextTask();
    }

    void OnMetadataWriteComplete(int result)
    {
        EXPECT_FALSE(metadata_writer_->IsWritePending());
        EXPECT_EQ(expected_write_result_, result);
        ScheduleNextTask();
    }

    void OnReadInfoComplete(int result)
    {
        EXPECT_FALSE(reader_->IsReadPending());
        EXPECT_EQ(expected_read_result_, result);
        ScheduleNextTask();
    }

    void OnReadComplete(int result)
    {
        EXPECT_FALSE(reader_->IsReadPending());
        read_callback_was_called_ = true;
        EXPECT_EQ(expected_read_result_, result);
        if (should_delete_reader_in_completion_callback_ && --reader_deletion_count_down_ == 0) {
            reader_.reset();
        }
        ScheduleNextTask();
    }

    // Helpers to work with HttpResponseInfo objects

    net::HttpResponseInfo* MakeHttpResponseInfo(const std::string& raw_headers)
    {
        net::HttpResponseInfo* info = new net::HttpResponseInfo;
        info->request_time = base::Time::Now();
        info->response_time = base::Time::Now();
        info->was_cached = false;
        info->headers = new net::HttpResponseHeaders(raw_headers);
        return info;
    }

    int GetHttpResponseInfoSize(const net::HttpResponseInfo* info)
    {
        base::Pickle pickle;
        return PickleHttpResonseInfo(&pickle, info);
    }

    bool CompareHttpResponseInfos(const net::HttpResponseInfo* info1,
        const net::HttpResponseInfo* info2)
    {
        base::Pickle pickle1;
        base::Pickle pickle2;
        PickleHttpResonseInfo(&pickle1, info1);
        PickleHttpResonseInfo(&pickle2, info2);
        return (pickle1.size() == pickle2.size()) && (0 == memcmp(pickle1.data(), pickle2.data(), pickle1.size()));
    }

    int PickleHttpResonseInfo(base::Pickle* pickle,
        const net::HttpResponseInfo* info)
    {
        const bool kSkipTransientHeaders = true;
        const bool kTruncated = false;
        info->Persist(pickle, kSkipTransientHeaders, kTruncated);
        return pickle->size();
    }

    // Helpers to fill and verify blocks of memory with a value

    void FillData(char value, char* data, int data_len)
    {
        memset(data, value, data_len);
    }

    bool CheckData(char value, const char* data, int data_len)
    {
        for (int i = 0; i < data_len; ++i, ++data) {
            if (*data != value)
                return false;
        }
        return true;
    }

    // Individual Tests ---------------------------------------------------------
    // Most of the individual tests involve multiple async steps. Each test
    // is delineated with a section header.

    // ReadNonExistentResponse -------------------------------------------
    void ReadNonExistentResponse()
    {
        // 1. Attempt to ReadInfo
        // 2. Attempt to ReadData

        reader_.reset(
            service_->storage()->CreateResponseReader(GURL(), kNoSuchResponseId));

        // Push tasks in reverse order
        PushNextTask(base::Bind(&AppCacheResponseTest::ReadNonExistentData,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::ReadNonExistentInfo,
            base::Unretained(this)));
        ScheduleNextTask();
    }

    void ReadNonExistentInfo()
    {
        EXPECT_FALSE(reader_->IsReadPending());
        read_info_buffer_ = new HttpResponseInfoIOBuffer();
        reader_->ReadInfo(read_info_buffer_.get(),
            base::Bind(&AppCacheResponseTest::OnReadInfoComplete,
                base::Unretained(this)));
        EXPECT_TRUE(reader_->IsReadPending());
        expected_read_result_ = net::ERR_CACHE_MISS;
    }

    void ReadNonExistentData()
    {
        EXPECT_FALSE(reader_->IsReadPending());
        read_buffer_ = new IOBuffer(kBlockSize);
        reader_->ReadData(read_buffer_.get(),
            kBlockSize,
            base::Bind(&AppCacheResponseTest::OnReadComplete,
                base::Unretained(this)));
        EXPECT_TRUE(reader_->IsReadPending());
        expected_read_result_ = net::ERR_CACHE_MISS;
    }

    // LoadResponseInfo_Miss ----------------------------------------------------
    void LoadResponseInfo_Miss()
    {
        PushNextTask(base::Bind(&AppCacheResponseTest::LoadResponseInfo_Miss_Verify,
            base::Unretained(this)));
        service_->storage()->LoadResponseInfo(GURL(), kNoSuchResponseId,
            storage_delegate_.get());
    }

    void LoadResponseInfo_Miss_Verify()
    {
        EXPECT_EQ(kNoSuchResponseId, storage_delegate_->loaded_info_id_);
        EXPECT_TRUE(!storage_delegate_->loaded_info_.get());
        TestFinished();
    }

    // LoadResponseInfo_Hit ----------------------------------------------------
    void LoadResponseInfo_Hit()
    {
        // This tests involves multiple async steps.
        // 1. Write a response headers and body to storage
        //   a. headers
        //   b. body
        // 2. Use LoadResponseInfo to read the response headers back out
        PushNextTask(base::Bind(&AppCacheResponseTest::LoadResponseInfo_Hit_Step2,
            base::Unretained(this)));
        writer_.reset(service_->storage()->CreateResponseWriter(GURL()));
        written_response_id_ = writer_->response_id();
        WriteBasicResponse();
    }

    void LoadResponseInfo_Hit_Step2()
    {
        writer_.reset();
        PushNextTask(base::Bind(&AppCacheResponseTest::LoadResponseInfo_Hit_Verify,
            base::Unretained(this)));
        service_->storage()->LoadResponseInfo(GURL(), written_response_id_,
            storage_delegate_.get());
    }

    void LoadResponseInfo_Hit_Verify()
    {
        EXPECT_EQ(written_response_id_, storage_delegate_->loaded_info_id_);
        EXPECT_TRUE(storage_delegate_->loaded_info_.get());
        EXPECT_TRUE(CompareHttpResponseInfos(
            write_info_buffer_->http_info.get(),
            storage_delegate_->loaded_info_->http_response_info()));
        EXPECT_EQ(basic_response_size(),
            storage_delegate_->loaded_info_->response_data_size());
        TestFinished();
    }

    // Metadata -------------------------------------------------
    void Metadata()
    {
        // This tests involves multiple async steps.
        // 1. Write a response headers and body to storage
        //   a. headers
        //   b. body
        // 2. Write metadata "Metadata First" using AppCacheResponseMetadataWriter.
        // 3. Check metadata was written.
        // 4. Write metadata "Second".
        // 5. Check metadata was written and was truncated .
        // 6. Write metadata "".
        // 7. Check metadata was deleted.

        // Push tasks in reverse order.
        PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_VerifyMetadata,
            base::Unretained(this), ""));
        PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_LoadResponseInfo,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_WriteMetadata,
            base::Unretained(this), ""));
        PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_VerifyMetadata,
            base::Unretained(this), "Second"));
        PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_LoadResponseInfo,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_WriteMetadata,
            base::Unretained(this), "Second"));
        PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_VerifyMetadata,
            base::Unretained(this), "Metadata First"));
        PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_LoadResponseInfo,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_WriteMetadata,
            base::Unretained(this), "Metadata First"));
        PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_ResetWriter,
            base::Unretained(this)));
        writer_.reset(service_->storage()->CreateResponseWriter(GURL()));
        written_response_id_ = writer_->response_id();
        WriteBasicResponse();
    }

    void Metadata_ResetWriter()
    {
        writer_.reset();
        ScheduleNextTask();
    }

    void Metadata_WriteMetadata(const char* metadata)
    {
        metadata_writer_.reset(service_->storage()->CreateResponseMetadataWriter(
            written_response_id_));
        scoped_refptr<IOBuffer> buffer(new WrappedIOBuffer(metadata));
        WriteResponseMetadata(buffer.get(), strlen(metadata));
    }

    void Metadata_LoadResponseInfo()
    {
        metadata_writer_.reset();
        storage_delegate_.reset(new MockStorageDelegate(this));
        service_->storage()->LoadResponseInfo(GURL(), written_response_id_,
            storage_delegate_.get());
    }

    void Metadata_VerifyMetadata(const char* metadata)
    {
        EXPECT_EQ(written_response_id_, storage_delegate_->loaded_info_id_);
        EXPECT_TRUE(storage_delegate_->loaded_info_.get());
        const net::HttpResponseInfo* read_head = storage_delegate_->loaded_info_->http_response_info();
        EXPECT_TRUE(read_head);
        const int metadata_size = strlen(metadata);
        if (metadata_size) {
            EXPECT_TRUE(read_head->metadata.get());
            EXPECT_EQ(metadata_size, read_head->metadata->size());
            EXPECT_EQ(0,
                memcmp(metadata, read_head->metadata->data(), metadata_size));
        } else {
            EXPECT_FALSE(read_head->metadata.get());
        }
        EXPECT_TRUE(CompareHttpResponseInfos(
            write_info_buffer_->http_info.get(),
            storage_delegate_->loaded_info_->http_response_info()));
        EXPECT_EQ(basic_response_size(),
            storage_delegate_->loaded_info_->response_data_size());
        ScheduleNextTask();
    }

    // AmountWritten ----------------------------------------------------

    void AmountWritten()
    {
        static const char kHttpHeaders[] = "HTTP/1.0 200 OK\0\0";
        std::string raw_headers(kHttpHeaders, arraysize(kHttpHeaders));
        net::HttpResponseInfo* head = MakeHttpResponseInfo(raw_headers);
        int expected_amount_written = GetHttpResponseInfoSize(head) + kNumBlocks * kBlockSize;

        // Push tasks in reverse order.
        PushNextTask(base::Bind(&AppCacheResponseTest::Verify_AmountWritten,
            base::Unretained(this), expected_amount_written));
        for (int i = 0; i < kNumBlocks; ++i) {
            PushNextTask(base::Bind(&AppCacheResponseTest::WriteOneBlock,
                base::Unretained(this), kNumBlocks - i));
        }
        PushNextTask(base::Bind(&AppCacheResponseTest::WriteResponseHead,
            base::Unretained(this), head));

        writer_.reset(service_->storage()->CreateResponseWriter(GURL()));
        written_response_id_ = writer_->response_id();
        ScheduleNextTask();
    }

    void Verify_AmountWritten(int expected_amount_written)
    {
        EXPECT_EQ(expected_amount_written, writer_->amount_written());
        TestFinished();
    }

    // WriteThenVariouslyReadResponse -------------------------------------------

    void WriteThenVariouslyReadResponse()
    {
        // This tests involves multiple async steps.
        // 1. First, write a large body using multiple writes, we don't bother
        //    with a response head for this test.
        // 2. Read the entire body, using multiple reads
        // 3. Read the entire body, using one read.
        // 4. Attempt to read beyond the EOF.
        // 5. Read just a range.
        // 6. Attempt to read beyond EOF of a range.

        // Push tasks in reverse order
        PushNextTask(base::Bind(&AppCacheResponseTest::ReadRangeFullyBeyondEOF,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::ReadRangePartiallyBeyondEOF,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::ReadPastEOF,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::ReadRange,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::ReadPastEOF,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::ReadAllAtOnce,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::ReadInBlocks,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::WriteOutBlocks,
            base::Unretained(this)));

        // Get them going.
        ScheduleNextTask();
    }

    void WriteOutBlocks()
    {
        writer_.reset(service_->storage()->CreateResponseWriter(GURL()));
        written_response_id_ = writer_->response_id();
        for (int i = 0; i < kNumBlocks; ++i) {
            PushNextTask(base::Bind(&AppCacheResponseTest::WriteOneBlock,
                base::Unretained(this), kNumBlocks - i));
        }
        ScheduleNextTask();
    }

    void WriteOneBlock(int block_number)
    {
        scoped_refptr<IOBuffer> io_buffer(
            new IOBuffer(kBlockSize));
        FillData(block_number, io_buffer->data(), kBlockSize);
        WriteResponseBody(io_buffer, kBlockSize);
    }

    void ReadInBlocks()
    {
        writer_.reset();
        reader_.reset(service_->storage()->CreateResponseReader(
            GURL(), written_response_id_));
        for (int i = 0; i < kNumBlocks; ++i) {
            PushNextTask(base::Bind(&AppCacheResponseTest::ReadOneBlock,
                base::Unretained(this), kNumBlocks - i));
        }
        ScheduleNextTask();
    }

    void ReadOneBlock(int block_number)
    {
        PushNextTask(base::Bind(&AppCacheResponseTest::VerifyOneBlock,
            base::Unretained(this), block_number));
        ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize);
    }

    void VerifyOneBlock(int block_number)
    {
        EXPECT_TRUE(CheckData(block_number, read_buffer_->data(), kBlockSize));
        ScheduleNextTask();
    }

    void ReadAllAtOnce()
    {
        PushNextTask(base::Bind(&AppCacheResponseTest::VerifyAllAtOnce,
            base::Unretained(this)));
        reader_.reset(service_->storage()->CreateResponseReader(
            GURL(), written_response_id_));
        int big_size = kNumBlocks * kBlockSize;
        ReadResponseBody(new IOBuffer(big_size), big_size);
    }

    void VerifyAllAtOnce()
    {
        char* p = read_buffer_->data();
        for (int i = 0; i < kNumBlocks; ++i, p += kBlockSize)
            EXPECT_TRUE(CheckData(i + 1, p, kBlockSize));
        ScheduleNextTask();
    }

    void ReadPastEOF()
    {
        EXPECT_FALSE(reader_->IsReadPending());
        read_buffer_ = new IOBuffer(kBlockSize);
        expected_read_result_ = 0;
        reader_->ReadData(read_buffer_.get(),
            kBlockSize,
            base::Bind(&AppCacheResponseTest::OnReadComplete,
                base::Unretained(this)));
    }

    void ReadRange()
    {
        PushNextTask(base::Bind(&AppCacheResponseTest::VerifyRange,
            base::Unretained(this)));
        reader_.reset(service_->storage()->CreateResponseReader(
            GURL(), written_response_id_));
        reader_->SetReadRange(kBlockSize, kBlockSize);
        ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize);
    }

    void VerifyRange()
    {
        EXPECT_TRUE(CheckData(2, read_buffer_->data(), kBlockSize));
        ScheduleNextTask(); // ReadPastEOF is scheduled next
    }

    void ReadRangePartiallyBeyondEOF()
    {
        PushNextTask(base::Bind(&AppCacheResponseTest::VerifyRangeBeyondEOF,
            base::Unretained(this)));
        reader_.reset(service_->storage()->CreateResponseReader(
            GURL(), written_response_id_));
        reader_->SetReadRange(kBlockSize, kNumBlocks * kBlockSize);
        ReadResponseBody(new IOBuffer(kNumBlocks * kBlockSize),
            kNumBlocks * kBlockSize);
        expected_read_result_ = (kNumBlocks - 1) * kBlockSize;
    }

    void VerifyRangeBeyondEOF()
    {
        // Just verify the first 1k
        VerifyRange();
    }

    void ReadRangeFullyBeyondEOF()
    {
        reader_.reset(service_->storage()->CreateResponseReader(
            GURL(), written_response_id_));
        reader_->SetReadRange((kNumBlocks * kBlockSize) + 1, kBlockSize);
        ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize);
        expected_read_result_ = 0;
    }

    // IOChaining -------------------------------------------
    void IOChaining()
    {
        // 1. Write several blocks out initiating the subsequent write
        //    from within the completion callback of the previous write.
        // 2. Read and verify several blocks in similarly chaining reads.

        // Push tasks in reverse order
        PushNextTaskAsImmediate(
            base::Bind(&AppCacheResponseTest::ReadInBlocksImmediately,
                base::Unretained(this)));
        PushNextTaskAsImmediate(
            base::Bind(&AppCacheResponseTest::WriteOutBlocksImmediately,
                base::Unretained(this)));

        // Get them going.
        ScheduleNextTask();
    }

    void WriteOutBlocksImmediately()
    {
        writer_.reset(service_->storage()->CreateResponseWriter(GURL()));
        written_response_id_ = writer_->response_id();
        for (int i = 0; i < kNumBlocks; ++i) {
            PushNextTaskAsImmediate(
                base::Bind(&AppCacheResponseTest::WriteOneBlock,
                    base::Unretained(this), kNumBlocks - i));
        }
        ScheduleNextTask();
    }

    void ReadInBlocksImmediately()
    {
        writer_.reset();
        reader_.reset(service_->storage()->CreateResponseReader(
            GURL(), written_response_id_));
        for (int i = 0; i < kNumBlocks; ++i) {
            PushNextTaskAsImmediate(
                base::Bind(&AppCacheResponseTest::ReadOneBlockImmediately,
                    base::Unretained(this),
                    kNumBlocks - i));
        }
        ScheduleNextTask();
    }

    void ReadOneBlockImmediately(int block_number)
    {
        PushNextTaskAsImmediate(base::Bind(&AppCacheResponseTest::VerifyOneBlock,
            base::Unretained(this), block_number));
        ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize);
    }

    // DeleteWithinCallbacks -------------------------------------------
    void DeleteWithinCallbacks()
    {
        // 1. Write out a few blocks normally, and upon
        //    completion of the last write, delete the writer.
        // 2. Read in a few blocks normally, and upon completion
        //    of the last read, delete the reader.

        should_delete_reader_in_completion_callback_ = true;
        reader_deletion_count_down_ = kNumBlocks;
        should_delete_writer_in_completion_callback_ = true;
        writer_deletion_count_down_ = kNumBlocks;

        PushNextTask(base::Bind(&AppCacheResponseTest::ReadInBlocks,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::WriteOutBlocks,
            base::Unretained(this)));
        ScheduleNextTask();
    }

    // DeleteWithIOPending -------------------------------------------
    void DeleteWithIOPending()
    {
        // 1. Write a few blocks normally.
        // 2. Start a write, delete with it pending.
        // 3. Start a read, delete with it pending.
        PushNextTask(base::Bind(&AppCacheResponseTest::ReadThenDelete,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::WriteThenDelete,
            base::Unretained(this)));
        PushNextTask(base::Bind(&AppCacheResponseTest::WriteOutBlocks,
            base::Unretained(this)));
        ScheduleNextTask();
    }

    void WriteThenDelete()
    {
        write_callback_was_called_ = false;
        WriteOneBlock(5);
        EXPECT_TRUE(writer_->IsWritePending());
        writer_.reset();
        ScheduleNextTask();
    }

    void ReadThenDelete()
    {
        read_callback_was_called_ = false;
        reader_.reset(service_->storage()->CreateResponseReader(
            GURL(), written_response_id_));
        ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize);
        EXPECT_TRUE(reader_->IsReadPending());
        reader_.reset();

        // Wait a moment to verify no callbacks.
        base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
            FROM_HERE, base::Bind(&AppCacheResponseTest::VerifyNoCallbacks, base::Unretained(this)),
            base::TimeDelta::FromMilliseconds(10));
    }

    void VerifyNoCallbacks()
    {
        EXPECT_TRUE(!write_callback_was_called_);
        EXPECT_TRUE(!read_callback_was_called_);
        TestFinished();
    }

    // Data members

    std::unique_ptr<base::WaitableEvent> test_finished_event_;
    std::unique_ptr<MockStorageDelegate> storage_delegate_;
    std::unique_ptr<MockAppCacheService> service_;
    std::stack<std::pair<base::Closure, bool>> task_stack_;

    std::unique_ptr<AppCacheResponseReader> reader_;
    scoped_refptr<HttpResponseInfoIOBuffer> read_info_buffer_;
    scoped_refptr<IOBuffer> read_buffer_;
    int expected_read_result_;
    bool should_delete_reader_in_completion_callback_;
    int reader_deletion_count_down_;
    bool read_callback_was_called_;

    int64_t written_response_id_;
    std::unique_ptr<AppCacheResponseWriter> writer_;
    std::unique_ptr<AppCacheResponseMetadataWriter> metadata_writer_;
    scoped_refptr<HttpResponseInfoIOBuffer> write_info_buffer_;
    scoped_refptr<IOBuffer> write_buffer_;
    int expected_write_result_;
    bool should_delete_writer_in_completion_callback_;
    int writer_deletion_count_down_;
    bool write_callback_was_called_;

    static std::unique_ptr<base::Thread> io_thread_;
};

// static
std::unique_ptr<base::Thread> AppCacheResponseTest::io_thread_;

TEST_F(AppCacheResponseTest, ReadNonExistentResponse)
{
    RunTestOnIOThread(&AppCacheResponseTest::ReadNonExistentResponse);
}

TEST_F(AppCacheResponseTest, LoadResponseInfo_Miss)
{
    RunTestOnIOThread(&AppCacheResponseTest::LoadResponseInfo_Miss);
}

TEST_F(AppCacheResponseTest, LoadResponseInfo_Hit)
{
    RunTestOnIOThread(&AppCacheResponseTest::LoadResponseInfo_Hit);
}

TEST_F(AppCacheResponseTest, Metadata)
{
    RunTestOnIOThread(&AppCacheResponseTest::Metadata);
}

TEST_F(AppCacheResponseTest, AmountWritten)
{
    RunTestOnIOThread(&AppCacheResponseTest::AmountWritten);
}

TEST_F(AppCacheResponseTest, WriteThenVariouslyReadResponse)
{
    RunTestOnIOThread(&AppCacheResponseTest::WriteThenVariouslyReadResponse);
}

TEST_F(AppCacheResponseTest, IOChaining)
{
    RunTestOnIOThread(&AppCacheResponseTest::IOChaining);
}

TEST_F(AppCacheResponseTest, DeleteWithinCallbacks)
{
    RunTestOnIOThread(&AppCacheResponseTest::DeleteWithinCallbacks);
}

TEST_F(AppCacheResponseTest, DeleteWithIOPending)
{
    RunTestOnIOThread(&AppCacheResponseTest::DeleteWithIOPending);
}

} // namespace content
